﻿using System;
using System.Collections.Generic;
using System.Linq;
using NDBGen.Configuration;
using NDBGen.Descriptors.Tables.Tokens;
using NDBGen.Plugins;
using NDBGen.Plugins.DataAccess;
using NDBGen.Plugins.Descriptors;

namespace NDBGen.DataAccess
{
    [DataAccessCodeGenerator("LinqToSql")]
    public class LinqToSqlDataAccessCodeGenerator : FileGeneratorBase<DataAccessGeneratorPluginData>, IDataAccessCodeFileGenerator
    {
        Boolean IDataAccessCodeFileGenerator.CanGenerateContextType { get { return true; } }

        String IDataAccessCodeFileGenerator.GetBaseContextOutputFilePath(SchemaDataAccessOutputElement config)
        {
            return "_DB.g.cs";
        }

        String IDataAccessCodeFileGenerator.GetContextOutputFilePath(SchemaDataAccessOutputElement config, String schemaName)
        {
            return String.Format(@"{0}\_DB.g.cs", schemaName.ToPascalCase());
        }

        String IDataAccessCodeFileGenerator.GetModelOutputFilePath(SchemaDataAccessOutputElement config, Table table)
        {
            return String.Format(@"{0}\{1}.g.cs", table.Schema.ToPascalCase(), table.TypeName);
        }

        void IDataAccessCodeFileGenerator.WriteBaseContext(SchemaDataAccessOutputElement config, IIndentedTextWriter writer)
        {
            var rootNamespace = config.GetSetting("rootNamespace");

            writer.WriteLine("using System;");
            writer.WriteLine("using System.Data;");
            writer.WriteLine("using System.Data.Linq;");
            writer.WriteLine("using System.Data.Linq.Mapping;");
            writer.WriteLine();
            writer.WriteLine("namespace {0}", rootNamespace);
            writer.WriteLine("{");
            using (writer.Indent())
            {
                writer.WriteLine("public partial class DB : DataContext");
                writer.WriteLine("{");
                using (writer.Indent())
                {
                    writer.WriteLine("public DB(IDbConnection connection)");
                    using (writer.Indent())
                    {
                        writer.WriteLine(": base(connection) { }");
                    }
                    writer.WriteLine();

                    writer.WriteLine("public DB(String fileOrServerOrConnection)");
                    using (writer.Indent())
                    {
                        writer.WriteLine(": base(fileOrServerOrConnection) { }");
                    }
                    writer.WriteLine();

                    writer.WriteLine("public DB(IDbConnection connection, MappingSource mapping)");
                    using (writer.Indent())
                    {
                        writer.WriteLine(": base(connection, mapping) { }");
                    }
                    writer.WriteLine();

                    writer.WriteLine("public DB(String fileOrServerOrConnection, MappingSource mapping)");
                    using (writer.Indent())
                    {
                        writer.WriteLine(": base(fileOrServerOrConnection, mapping) { }");
                    }
                }
                writer.WriteLine("}");
            }
            writer.WriteLine("}");
        }

        void IDataAccessCodeFileGenerator.WriteContextType(SchemaDataAccessOutputElement config, IEnumerable<Table> tables, IIndentedTextWriter writer)
        {
            var rootNamespace = config.GetSetting("rootNamespace");
            var schema = tables.Select(t => t.Schema).Distinct();
            if (schema.Count() > 1) throw new ArgumentOutOfRangeException("tables", "To use this method, all tables must be in the same schema.");
            var usingNamespace = rootNamespace + "." + schema.First().ToPascalCase();

            writer.WriteLine("using System.Data.Linq;");
            writer.WriteLine("using {0};", usingNamespace);
            writer.WriteLine();
            writer.WriteLine("namespace {0}", rootNamespace);
            writer.WriteLine("{");
            using (writer.Indent())
            {
                writer.WriteLine("public partial class DB");
                writer.WriteLine("{");
                using (writer.Indent())
                {
                    var contextTables = tables.Where(t => !t.WasReplacedByEnum);
                    foreach (var table in contextTables) writer.WriteLine(String.Format("public Table<{0}> {1} {{ get {{ return this.GetTable<{0}>(); }} }}", table.TypeName, table.Name.ToPascalCase()));
                }
                writer.WriteLine("}");
            }
            writer.WriteLine("}");
        }

        public void WriteModel(SchemaDataAccessOutputElement config, Table table, IIndentedTextWriter writer)
        {
            var rootNamespace = config.GetSetting("rootNamespace");
            if (rootNamespace.EndsWith(".")) rootNamespace = rootNamespace.TrimEnd('.');

            var enumTokenDescriptor = (EnumTokenDescriptor)table.Descriptors.SingleOrDefault(t => t.GetType() == typeof(EnumTokenDescriptor));

            var customAttributes = (
                from descriptor in table.Descriptors
                let generator = this.GetGeneratorForDescriptor(descriptor)
                from attribute in ((IDotNetCodeGenerator)generator).GetAttributes()
                select attribute).SortOrdered();

            writer.WriteLine("using System;");
            writer.WriteLine("using System.ComponentModel.DataAnnotations;");
            writer.WriteLine("using System.Data.Linq.Mapping;");
            if (table.Descriptors.Any(t => t.GetType() == typeof(DataContractTokenDescriptor)))
            {
                writer.WriteLine("using System.Runtime.Serialization;");
            }
            writer.WriteLine("using System.Xml.Linq;");
            writer.WriteLine();
            writer.WriteLine("namespace " + rootNamespace + "." + table.Schema.ToPascalCase());
            writer.WriteLine("{");

            using (writer.Indent())
            {
                if (enumTokenDescriptor != null)
                {
                    var enumTokenCodeGenerator = (IAuxilliaryCodeGenerator)this.GetGeneratorForDescriptor(enumTokenDescriptor);
                    enumTokenCodeGenerator.WriteAuxilliaryCode(table, writer);
                }
                if (enumTokenDescriptor == null || !table.CanBeRepresentedByEnum)
                {
                    writer.Write("[Table(Name = \"{0}.{1}\")]", table.Schema, table.Name);
                    writer.WriteLine();
                    foreach (var attr in customAttributes)
                    {
                        writer.WriteLine(attr);
                    }
                    writer.Write("public partial class {0}", table.TypeName);
                    writer.WriteLine();
                    writer.WriteLine("{");
                    using (writer.Indent())
                    {
                        foreach (var col in table.Columns)
                        {
                            if (col != table.Columns.First())
                            {
                                writer.WriteLine();
                            }
                            this.GenerateProperty(table, col, writer);
                        }
                    }
                    writer.WriteLine("}");
                }
                else table.WasReplacedByEnum = true;
            }
            writer.WriteLine("}");
        }

        private void GenerateProperty(Table table, Column column, IIndentedTextWriter writer)
        {
            var generator = this.GetGeneratorForDescriptor(column.DataTypeDescriptor);            
            generator.WriteProperty(writer);

            /*
                column.DataTypeDescriptor.GenerateColumnAttributeDeclarations().Union(
                from m in column.DescriptorList
                from dec in m.GenerateColumnAttributeDeclarations()
                select dec).SortOrdered();
            var columnAttributeDecs = columnAttributeDecsQuery.Distinct().ToArray();

            var isNullable = column.DescriptorList.Any(t => t.GetType() == typeof(Nullable) && !String.IsNullOrEmpty(t.ToString()));

            for (var index = 0; index < columnAttributeDecs.Length; index++)
            {
                var dec = columnAttributeDecs[index];
                writer.Write(", ");
                if (dec.Equals(ColumnMetadataHelper.ColumnAttribute.DbType.Format))
                {
                    dec = ((OrderedKeyedMetadataValue)dec).AddArgs(isNullable ? String.Empty : " not");
                }
                writer.Write(dec);
            }

            writer.Write(")]");
            writer.WriteLine();

            var customAttributes =
                column.DataTypeDescriptor.GenerateCustomAttributeDeclarations().Union(
                from m in column.DescriptorList
                from c in m.GenerateCustomAttributeDeclarations()
                select c).SortOrdered();

            foreach (var customAttribute in customAttributes.Distinct())
            {
                writer.WriteLine(customAttribute);
            }

            writer.Write(column.DataTypeDescriptor.GenerateCSharpProperty(table, column.DescriptorList.ToArray()));

            writer.WriteLine();
            writer.WriteLine();*/
        }
    }
}